package com.blog.cmrpersonalblog.service.impl;

import com.blog.cmrpersonalblog.config.EmailConfig;
import com.blog.cmrpersonalblog.dto.auth.request.ForgotPasswordRequest;
import com.blog.cmrpersonalblog.dto.auth.request.ResetPasswordRequest;
import com.blog.cmrpersonalblog.dto.auth.response.EmailVerificationResponse;
import com.blog.cmrpersonalblog.entity.PasswordResetToken;
import com.blog.cmrpersonalblog.entity.SysUser;
import com.blog.cmrpersonalblog.mapper.PasswordResetTokenMapper;
import com.blog.cmrpersonalblog.service.PasswordResetService;
import com.blog.cmrpersonalblog.service.SysUserService;
import com.blog.cmrpersonalblog.service.UserActivityService;
import com.blog.cmrpersonalblog.utils.PasswordUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import jakarta.mail.internet.MimeMessage;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 密码重置服务实现类
 */
@Slf4j
@Service
public class PasswordResetServiceImpl implements PasswordResetService {

   @Resource
    private PasswordResetTokenMapper passwordResetTokenMapper;

   @Resource
    private SysUserService sysUserService;

   @Resource
    private UserActivityService userActivityService;

   @Resource
    private JavaMailSender mailSender;

   @Resource
    private TemplateEngine templateEngine;

   @Resource
    private EmailConfig emailConfig;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public EmailVerificationResponse sendResetPasswordEmail(ForgotPasswordRequest request, 
                                                           String ipAddress, String userAgent) {
        try {
            String email = request.getEmail();

            // 1. 检查用户是否存在
            SysUser user = sysUserService.getUserByEmail(email);
            if (user == null) {
                // 为了安全，不暴露用户是否存在，统一返回成功消息
                return EmailVerificationResponse.success(
                    "如果该邮箱已注册，重置链接将发送到您的邮箱", 
                    emailConfig.getResetTokenExpireHours() * 60
                );
            }

            // 2. 检查用户状态
            if (user.getStatus() != 1) {
                return EmailVerificationResponse.error("账户已被禁用，无法重置密码");
            }

            // 3. 检查发送频率限制
            if (!canSendResetEmail(user.getUserId(), email)) {
                Integer remainingCount = getRemainingDailyCount(user.getUserId(), email);
                return EmailVerificationResponse.rateLimited(
                    "发送过于频繁，请稍后再试", 
                    emailConfig.getSendIntervalSeconds(), 
                    remainingCount
                );
            }

            // 4. 检查每日发送限制
            LocalDateTime today = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
            Integer todayCount = passwordResetTokenMapper.countTodaySent(user.getUserId(), email, today);
            if (todayCount >= emailConfig.getDailyLimit()) {
                return EmailVerificationResponse.error("今日发送次数已达上限，请明天再试");
            }

            // 5. 使之前的重置令牌失效
            invalidateResetTokens(user.getUserId(), email);

            // 6. 生成新的重置令牌
            String resetToken = generateResetToken();

            // 7. 保存重置令牌记录
            PasswordResetToken tokenRecord = new PasswordResetToken();
            tokenRecord.setUserId(user.getUserId());
            tokenRecord.setEmail(email);
            tokenRecord.setResetToken(resetToken);
            tokenRecord.setIsUsed(0);
            tokenRecord.setExpireTime(LocalDateTime.now().plusHours(emailConfig.getResetTokenExpireHours()));
            tokenRecord.setIpAddress(ipAddress);
            tokenRecord.setUserAgent(userAgent);
            tokenRecord.setCreateTime(LocalDateTime.now());

            passwordResetTokenMapper.insert(tokenRecord);

            // 8. 异步发送重置邮件
            sendResetPasswordEmailAsync(email, resetToken, user.getNickName());

            // 9. 记录活动
            Map<String, Object> extraData = new HashMap<>();
            extraData.put("email", email);
            extraData.put("resetToken", resetToken);
            userActivityService.recordActivity(user.getUserId(), "PASSWORD_RESET_REQUEST", "请求密码重置",
                user.getUserId(), "user", "SUCCESS", extraData);

            log.info("密码重置邮件发送成功: userId={}, email={}", user.getUserId(), email);

            return EmailVerificationResponse.success(
                "重置链接已发送到您的邮箱，请查收", 
                emailConfig.getResetTokenExpireHours() * 60
            );

        } catch (Exception e) {
            log.error("发送密码重置邮件失败: email={}, error={}", request.getEmail(), e.getMessage(), e);
            return EmailVerificationResponse.error("发送失败，请稍后重试");
        }
    }

    @Override
    public boolean validateResetToken(String resetToken) {
        try {
            PasswordResetToken tokenRecord = passwordResetTokenMapper.selectValidToken(resetToken);

            if (tokenRecord == null) {
                log.warn("重置令牌验证失败: token={} - 令牌不存在", resetToken);
                return false;
            }

            if (tokenRecord.getIsUsed() == 1) {
                log.warn("重置令牌验证失败: token={} - 令牌已使用", resetToken);
                return false;
            }

            if (tokenRecord.getExpireTime().isBefore(LocalDateTime.now())) {
                log.warn("重置令牌验证失败: token={} - 令牌已过期", resetToken);
                return false;
            }

            return true;
        } catch (Exception e) {
            log.error("重置令牌验证异常: token={}, error={}", resetToken, e.getMessage());
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean resetPassword(ResetPasswordRequest request, String ipAddress, String userAgent) {
        try {
            String resetToken = request.getResetToken();
            String newPassword = request.getNewPassword();
            String confirmPassword = request.getConfirmPassword();

            // 1. 验证密码确认
            if (!newPassword.equals(confirmPassword)) {
                log.warn("密码重置失败: token={} - 两次输入的密码不一致", resetToken);
                return false;
            }

            // 2. 验证重置令牌
            if (!validateResetToken(resetToken)) {
                return false;
            }

            // 3. 获取令牌记录
            PasswordResetToken tokenRecord = passwordResetTokenMapper.selectValidToken(resetToken);
            if (tokenRecord == null) {
                return false;
            }

            // 4. 获取用户信息
            SysUser user = sysUserService.getById(tokenRecord.getUserId());
            if (user == null) {
                log.warn("密码重置失败: token={} - 用户不存在", resetToken);
                return false;
            }

            // 5. 检查用户状态
            if (user.getStatus() != 1) {
                log.warn("密码重置失败: token={} - 用户已被禁用", resetToken);
                return false;
            }

            // 6. 加密新密码
            String encryptedPassword = PasswordUtils.sha256EncryptWithUserSalt(newPassword, user.getUserName());

            // 7. 更新用户密码
            user.setPassword(encryptedPassword);
            user.setUpdateTime(LocalDateTime.now());
            boolean updateResult = sysUserService.updateById(user);

            if (!updateResult) {
                log.error("密码重置失败: token={} - 密码更新失败", resetToken);
                return false;
            }

            // 8. 标记令牌为已使用
            tokenRecord.setIsUsed(1);
            tokenRecord.setUseTime(LocalDateTime.now());
            passwordResetTokenMapper.updateById(tokenRecord);

            // 9. 使该用户的所有其他重置令牌失效
            invalidateResetTokens(user.getUserId(), user.getEmail());

            // 10. 记录活动
            Map<String, Object> extraData = new HashMap<>();
            extraData.put("resetToken", resetToken);
            extraData.put("ipAddress", ipAddress);
            userActivityService.recordActivity(user.getUserId(), "PASSWORD_RESET_SUCCESS", "密码重置成功",
                user.getUserId(), "user", "SUCCESS", extraData);

            log.info("密码重置成功: userId={}, email={}", user.getUserId(), user.getEmail());
            return true;

        } catch (Exception e) {
            log.error("密码重置异常: token={}, error={}", request.getResetToken(), e.getMessage(), e);
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean invalidateResetTokens(Long userId, String email) {
        try {
            Integer count = passwordResetTokenMapper.invalidateUserTokens(userId, email);
            log.info("重置令牌失效成功: userId={}, email={}, count={}", userId, email, count);
            return count > 0;
        } catch (Exception e) {
            log.error("重置令牌失效失败: userId={}, email={}, error={}", userId, email, e.getMessage());
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer cleanExpiredTokens() {
        try {
            LocalDateTime expireTime = LocalDateTime.now().minusDays(7); // 清理7天前的过期记录
            Integer count = passwordResetTokenMapper.deleteExpiredTokens(expireTime);
            log.info("清理过期重置令牌成功: count={}", count);
            return count;
        } catch (Exception e) {
            log.error("清理过期重置令牌失败: error={}", e.getMessage());
            return 0;
        }
    }

    @Override
    public boolean canSendResetEmail(Long userId, String email) {
        try {
            LocalDateTime limitTime = LocalDateTime.now().minusSeconds(emailConfig.getSendIntervalSeconds());
            Integer recentCount = passwordResetTokenMapper.countSentInTimeRange(userId, email, limitTime);
            return recentCount == 0;
        } catch (Exception e) {
            log.error("检查发送频率限制失败: userId={}, email={}, error={}", userId, email, e.getMessage());
            return false;
        }
    }

    @Override
    public Integer getRemainingDailyCount(Long userId, String email) {
        try {
            LocalDateTime today = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
            Integer todayCount = passwordResetTokenMapper.countTodaySent(userId, email, today);
            return Math.max(0, emailConfig.getDailyLimit() - todayCount);
        } catch (Exception e) {
            log.error("获取剩余发送次数失败: userId={}, email={}, error={}", userId, email, e.getMessage());
            return 0;
        }
    }

    @Override
    public String generateResetToken() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 异步发送密码重置邮件
     */
    @Async("emailTaskExecutor")
    public void sendResetPasswordEmailAsync(String email, String resetToken, String nickName) {
        try {
            sendResetPasswordEmail(email, resetToken, nickName);
            log.info("异步密码重置邮件发送成功: email={}", email);
        } catch (Exception e) {
            log.error("异步密码重置邮件发送失败: email={}, error={}", email, e.getMessage());
            // 异步发送失败不影响主流程，只记录日志
        }
    }

    /**
     * 发送密码重置邮件
     */
    private void sendResetPasswordEmail(String email, String resetToken, String nickName) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

            helper.setFrom(emailConfig.getFromEmail(), emailConfig.getFromName());
            helper.setTo(email);
            helper.setSubject(emailConfig.getTemplates().getResetPasswordSubject());

            // 构建重置链接
            String resetUrl = emailConfig.getTemplates().getResetPasswordUrl() + "?token=" + resetToken;

            // 使用Thymeleaf模板生成邮件内容
            Context context = new Context();
            context.setVariable("nickName", nickName);
            context.setVariable("resetUrl", resetUrl);
            context.setVariable("resetToken", resetToken);
            context.setVariable("expireHours", emailConfig.getResetTokenExpireHours());

            String htmlContent = templateEngine.process("email/reset-password", context);
            helper.setText(htmlContent, true);

            mailSender.send(message);
            log.info("密码重置邮件发送成功: email={}", email);

        } catch (Exception e) {
            log.error("密码重置邮件发送失败: email={}, error={}", email, e.getMessage());
            throw new RuntimeException("邮件发送失败", e);
        }
    }
}
